/*
Java board game library.
Copyright (C) 2011  Deepesh Garg

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package d.g.boardgames.chess.gui;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingWorker;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

import d.g.boardgames.BoardGUI;
import d.g.boardgames.GameData;
import d.g.boardgames.MoveData;
import d.g.boardgames.chess.AnalysisData;
import d.g.boardgames.chess.AnalysisListener;
import d.g.boardgames.chess.ChessBoard;
import d.g.boardgames.chess.ChessBoardRenderProperties;
import d.g.boardgames.chess.ChessGame;
import d.g.boardgames.chess.ChessGameAnalyser;
import d.g.boardgames.chess.ChessGameData;
import d.g.boardgames.chess.ChessGameListener;
import d.g.boardgames.chess.ChessMove;
import d.g.boardgames.chess.ChessMoveParser;
import d.g.boardgames.chess.CoordinateMoveParser;
import d.g.boardgames.chess.CraftyPositionAnalyser;
import d.g.boardgames.chess.FENSerializer;
import d.g.boardgames.chess.PGNMoveParser;
import d.g.boardgames.chess.ChessCellState.PieceColor;
import d.g.boardgames.chess.player.GuiListenerMoveSource;
import d.g.boardgames.chess.player.Player;
import d.g.boardgames.chess.player.SourceDelegatePlayer;
import d.g.boardgames.chess.player.XBoardSource;
import d.g.boardgames.chess.util.FileHelper;


public class ChessGUI extends JFrame {

	ChessBoardGUI boardGUI;
	ChessBoardRenderProperties boardRenderProperties;
	
	ChessMoveParser moveParser = new PGNMoveParser();
	
	ChessBoard chessBoard;
	Player whitePlayer;
	Player blackPlayer;
	JCheckBox whitePlayerComputer;
	JCheckBox blackPlayerComputer;
	
	Player computerWhitePlayer;
	Player computerBlackPlayer;
	Player humanWhitePlayer;
	Player humanBlackPlayer;

	ChessGame chessGame;
	ChessGameAnalyser chessGameAnalyser;
	
	JCheckBox flipBoard;
	JPanel commandPanel;
	JTextField commandText;
	JButton actionButton;
	JPanel superCommandPanel;
	JPanel boardAndCommandContainer;
	
	JPanel infoPanel;
	JTabbedPane moveAnalysisTabPanel;
	
	JButton backOneMove;
	JButton truncateGame;
	
	JPanel analysisScrollAndCommandContainer;
	JScrollPane analysisScrollPanel;
	JTable analysisTable;
	JPanel analysisCommandPanel;
	JCheckBox analyserToggle;
	JCheckBox showHint;
	
	JPanel dataPanel;
	
	//TableModelMoveData tableModelMoveData;
	TableModelAnalysisData tableModelAnalysisData;
	
	JLabel moveExtraInfo;
	
	AnalysisTableRowSelectionListener analysisTableRowSelectionListener;
	
	JMenuBar menuBar;
	GameDataEditorDialog gameDataEditorDialog;
	
	String lastDir = "." ;
	
	String theme = "default" ;
	String currentGameFileName = "currentGame.pgn";

	public ChessGUI() {
	}
	
	public void initGUI(Map<String, String> commandLineArgs) {
		if (commandLineArgs.get("--theme") != null) {
			setTheme(commandLineArgs.get("--theme"));
		}
		try {
			Image taskBarIcon = ImageIO.read (getClass().getResource("/Data/chess/images/chess-icon32x32.png")) ;
			setIconImage(taskBarIcon);
		} catch (Exception e) {
			e.printStackTrace();
		}
		setTitle("Java Chess");
		JSplitPane mainSplitPane = new JSplitPane();
		JSplitPane infoSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
		setSize (new Dimension (1000, 520)) ;
		chessBoard = new ChessBoard() ;
		boardGUI = new ChessBoardGUI(chessBoard) ;
		commandPanel = new JPanel();
		flipBoard = new JCheckBox("Flip Board");
		flipBoard.addItemListener(new FlipBoardItemListener());
		commandPanel.add(flipBoard);
		whitePlayerComputer = new JCheckBox("White Computer");
		whitePlayerComputer.addItemListener(new WhiteComputerItemListener());
		commandPanel.add(whitePlayerComputer);
		blackPlayerComputer = new JCheckBox("Black Computer");
		blackPlayerComputer.addItemListener(new BlackComputerItemListener());
		commandPanel.add(blackPlayerComputer);
		superCommandPanel = new  JPanel();
		commandText = new JTextField();
		superCommandPanel.setLayout(new BorderLayout());
		superCommandPanel.add(commandText);
		actionButton = new JButton ("Go..");
		actionButton.addActionListener(new GoButtonAction());
		superCommandPanel.add(actionButton, BorderLayout.EAST);
		boardAndCommandContainer = new JPanel();
		boardAndCommandContainer.setLayout(new BorderLayout());
		boardAndCommandContainer.add(commandPanel, BorderLayout.NORTH);
		boardAndCommandContainer.add(boardGUI);
		boardAndCommandContainer.add(superCommandPanel, BorderLayout.SOUTH);
		
		boardRenderProperties = new ChessBoardRenderProperties(theme);
		boardGUI.setBoardRenderProperties(boardRenderProperties);
		
		infoPanel = new JPanel();
		moveAnalysisTabPanel = new JTabbedPane();
		moveAnalysisTabPanel.setMinimumSize(new Dimension(50,50));
		truncateGame = new JButton("Truncate");
		truncateGame.addActionListener(new TruncateButtonAction());
		backOneMove = new JButton("< Back");
		backOneMove.addActionListener(new BackButtonAction());
		tableModelAnalysisData = new TableModelAnalysisData();
		analysisTable = new JTable(tableModelAnalysisData);
		analysisTable.setDefaultRenderer(Object.class, new AnalysisRenderer());
		analysisTable.getColumnModel().getColumn(0).setMaxWidth(100);
		analysisTable.setAutoCreateColumnsFromModel(false);
		analysisScrollPanel = new JScrollPane(analysisTable);
		analyserToggle = new JCheckBox("Stop Analysing");
		analyserToggle.addItemListener(new ToggleAnalyserItemListener());
		showHint = new JCheckBox ("Show Hint");
		showHint.addItemListener(new ToggleHintListener(boardRenderProperties));
		analysisCommandPanel = new JPanel();
		analysisCommandPanel.add(backOneMove);
		analysisCommandPanel.add(truncateGame);
		analysisCommandPanel.add(analyserToggle);
		analysisCommandPanel.add(showHint);
		analysisScrollAndCommandContainer = new JPanel();
		analysisScrollAndCommandContainer.setLayout(new BorderLayout());
		analysisScrollAndCommandContainer.add(analysisCommandPanel, BorderLayout.NORTH);
		analysisScrollAndCommandContainer.add(analysisScrollPanel);
		
		moveAnalysisTabPanel.add("Analysis", analysisScrollAndCommandContainer);
		dataPanel = new JPanel();
		dataPanel.setLayout(new BorderLayout());
		infoSplitPane.setTopComponent(moveAnalysisTabPanel);
		infoSplitPane.setBottomComponent(dataPanel);
		infoSplitPane.setDividerLocation(300);
		infoPanel.setLayout(new BorderLayout());
		infoPanel.add(infoSplitPane);
		
		mainSplitPane.setLeftComponent(boardAndCommandContainer);
		mainSplitPane.setRightComponent(infoPanel);
		add (mainSplitPane) ;
		mainSplitPane.setDividerLocation(450);

		//Create table
		//tableModelMoveData = new TableModelMoveData();
		
		moveExtraInfo = new JLabel();
		dataPanel.add(moveExtraInfo);
		
		analysisTableRowSelectionListener = new AnalysisTableRowSelectionListener(tableModelAnalysisData);
		analysisTable.getSelectionModel().addListSelectionListener(analysisTableRowSelectionListener);
		
		gameDataEditorDialog = new GameDataEditorDialog(this);
		
		menuBar = new JMenuBar();
		setJMenuBar(menuBar);
		JMenu fileMenu = new JMenu("File");
		JMenuItem newGame = new JMenuItem("New Game");
		JMenuItem enterData = new JMenuItem("Enter Data");
		JMenuItem load = new JMenuItem("Load ..");
		JMenuItem saveAs = new JMenuItem("Save as ..");
		fileMenu.add(newGame);
		fileMenu.add(enterData);
		fileMenu.add(load);
		fileMenu.add(saveAs);
		menuBar.add(fileMenu);

		newGame.setAction(new AbstractAction("New Game") {
			@Override
			public void actionPerformed(ActionEvent ae) {
				handleNewGame();
			}
		});
		
		load.setAction(new AbstractAction("Load ...") {
			@Override
			public void actionPerformed(ActionEvent ae) {
				handleLoad();
			}
		});

		saveAs.setAction(new AbstractAction("Save as ...") {
			@Override
			public void actionPerformed(ActionEvent ae) {
				handleSaveAs();
			}
		});

		enterData.setAction(new AbstractAction("Enter Data") {
			@Override
			public void actionPerformed(ActionEvent ae) {
				handleEnterData();
			}
		});
		
		addWindowListener(new GameWindowListener());
		setVisible(true) ;
	}

	public static void main (String argv[]) throws Exception {
		ChessGUI chessGUI = new ChessGUI() ;
		Map<String, String> commandLineArgs = chessGUI.parseCommandLine(argv);
		chessGUI.initGUI(commandLineArgs);
		chessGUI.initGame(commandLineArgs);
		chessGUI.startGame();
	}

	protected Map<String, String> parseCommandLine(String argv[]) {
		Map<String, String> parsedArguments = new HashMap<String, String>();
		for (String arg: argv) {
			int idx ;
			if ((idx = arg.indexOf("=")) != -1) {
				String argName = arg.substring(0, idx);
				String argValue = arg.substring(idx+1, arg.length());
				parsedArguments.put(argName, argValue);
			}
		}
		return parsedArguments;
	}
	
	public void initGame(Map<String, String> commandLineArgs) {
		GuiListenerMoveSource whiteGuiMoveSource = new GuiListenerMoveSource();
		whiteGuiMoveSource.setRenderProperties(boardRenderProperties);
		boardGUI.addBoardEventListener(whiteGuiMoveSource);
		
		GuiListenerMoveSource blackGuiMoveSource = new GuiListenerMoveSource();
		blackGuiMoveSource.setRenderProperties(boardRenderProperties);
		boardGUI.addBoardEventListener(blackGuiMoveSource);
		
		XBoardSource xboardMoveSourceBlack = new XBoardSource();
		XBoardSource xboardMoveSourceWhite = new XBoardSource();
		computerBlackPlayer = new SourceDelegatePlayer (PieceColor.BLACK, xboardMoveSourceBlack);
		humanBlackPlayer = new SourceDelegatePlayer (PieceColor.BLACK, blackGuiMoveSource);
		
		computerWhitePlayer = new SourceDelegatePlayer (PieceColor.WHITE, xboardMoveSourceWhite);
		humanWhitePlayer = new SourceDelegatePlayer (PieceColor.WHITE, whiteGuiMoveSource);
		
		whitePlayer = humanWhitePlayer;
		blackPlayer = humanBlackPlayer;
	}
			
	public void startGame() {
		chessGame = new ChessGame(whitePlayer, blackPlayer, chessBoard);
		ChessGameListenerGui chessGameListenerGui = new ChessGameListenerGui();
		chessGame.addGameListener(chessGameListenerGui);
		chessGameAnalyser = new ChessGameAnalyser(new CraftyPositionAnalyser(2,10));
		ChessGameListenerAnalyser chessGameListenerAnalyser = new ChessGameListenerAnalyser();
		chessGame.addGameListener(chessGameListenerAnalyser);
		//chessGame.addGameListener(new ChessGameListenerMoveTable(tableModelMoveData));
		AnalysisTableUpdater analysisTableUpdater = new AnalysisTableUpdater(tableModelAnalysisData);
		chessGame.addGameListener(analysisTableUpdater);
		chessGame.addGameListener(new ScrollUpdater());
		CurrentGameWriter currentGameWriter = new CurrentGameWriter();
		chessGame.addGameListener(currentGameWriter);
		chessGameAnalyser.addAnalysisListener(currentGameWriter);
		chessGameAnalyser.addAnalysisListener(analysisTableUpdater);
		chessGameAnalyser.addAnalysisListener(chessGameListenerGui);
		
		String pgnData = FileHelper.readFile(currentGameFileName);
		ChessGameData gameData = new ChessGameData();
		gameData.fromPgn(pgnData);
	    chessGame.setGameData(gameData);
	    
	    chessGame.startGame();
		chessGameAnalyser.startAnalysis();
	}

	public void setTheme(String theme) {
		this.theme = theme;
	}
	
	public void addMoveToList(ChessBoard boardBefore, ChessBoard boardAfter, ChessMove chessMove) {
		JLabel label = new JLabel(moveParser.createMoveString(boardBefore, chessMove));
		MoveLabelListener labelListener = new MoveLabelListener();
		labelListener.setBoardAfter(boardAfter);
		labelListener.setBoardBefore(boardBefore);
		labelListener.setBoardGUI(boardGUI);
		label.addMouseListener(labelListener);
	}
	
	private void handleNewGame() {
		chessGame.stopGame();
		chessGame.setChessBoard(new ChessBoard());
		chessGame.startGame();
	}
	
	private void handleLoad() {
		JFileChooser chooser = new JFileChooser(lastDir);
		int returnVal = chooser.showOpenDialog(this);
		 if(returnVal == JFileChooser.APPROVE_OPTION) {
			 lastDir = chooser.getSelectedFile().getParent();
			 String pgnData = FileHelper.readFile(chooser.getSelectedFile());
			 ChessGameData gameData = new ChessGameData();
			 gameData.fromPgn(pgnData);
		     chessGame.setGameData(gameData);
		 }
	}
	
	private void handleSaveAs() {
		JFileChooser chooser = new JFileChooser(lastDir);
		chooser.setDialogTitle("Save PGN");
		chooser.setApproveButtonText("Save");
		int returnVal = chooser.showOpenDialog(this);
		 if(returnVal == JFileChooser.APPROVE_OPTION) {
			 lastDir = chooser.getSelectedFile().getParent();
			 if (chooser.getSelectedFile().exists()) {
				 JOptionPane okCancel = new JOptionPane("File exists. Overwrite?", JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION) ;
				 JDialog okCancelDialog = okCancel.createDialog("Confirm file overwrite");
				 okCancelDialog.setVisible(true);
				 int confirmationResult = (Integer) okCancel.getValue();
				 if (confirmationResult == JOptionPane.OK_CANCEL_OPTION) {
					 return;
				 }
			 }
			 String pgnData = chessGame.getGameData().getPgn();
			 FileHelper.writeFile(chooser.getSelectedFile(), pgnData);
		 }
	}
	
	private void handleEnterData() {
		GameDataEditorDialog gameDataEditor = new GameDataEditorDialog(this);
		gameDataEditor.setVisible(true);
		String pgnData = gameDataEditor.getText();
		if (pgnData != null && !pgnData.trim().equals("")) {
			ChessGameData gameData = new ChessGameData();
			gameData.fromPgn(pgnData);
		    chessGame.setGameData(gameData);
		}
	}
	
	class ChessGameListenerGui implements ChessGameListener, AnalysisListener {
		ChessBoard chessBoard;
		public void onGameUpdate (GameData<ChessBoard, ChessMove> gameData) {
			chessBoard = gameData.getLastEntry().getBoard();
			boardGUI.setBoard(chessBoard);
			int moveListSize = gameData.getMoveDataList().size();
			ChessMove lastMove = moveListSize > 1? gameData.getMoveDataList().get(moveListSize - 2).getMove() : null;
			boardGUI.setLastMove(lastMove);
			displayComment(gameData.getLastEntry().getBoard());
		}

		@Override
		public void onAnalysisUpdate(ChessGameData chessGameData) {
			//chessBoard = chessGameData.getLastEntry().getBoard();
			//boardGUI.setBoard(chessBoard);
			//displayComment(chessGameData.getLastEntry().getBoard());
		}
	}
	
	class ChessGameListenerAnalyser implements ChessGameListener {		
		public void onGameUpdate (GameData<ChessBoard, ChessMove> gameData) {
			chessGameAnalyser.setGameData(gameData);
		}
	}
	
	class GameWindowListener extends WindowAdapter {
		
		public void windowClosing(WindowEvent windowEvent) {
			try {
				chessGame.stopGame();
				computerBlackPlayer.abortMove();
				computerWhitePlayer.abortMove();
				try {Thread.sleep(100);} catch (InterruptedException ie){}
			} finally {
				System.exit(0);
			}
		}
	}

	/*
	class ChessGameListenerMoveTable implements ChessGameListener {
		
		public TableModelMoveData tableModelMoveData;
		
		public ChessGameListenerMoveTable(TableModelMoveData tableModelMoveData) {
			this.tableModelMoveData = tableModelMoveData;
		}
		
		public void onGameUpdate (GameData<ChessBoard, ChessMove> gameData) {
			tableModelMoveData.gameData = (ChessGameData)gameData;
			SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
				public Void doInBackground() {
					tableModelMoveData.fireTableStructureChanged();
					return null;
				}
			};
			worker.execute();
		}
	}
	*/

	class ScrollUpdater implements ChessGameListener {
		
		public void onGameUpdate (GameData<ChessBoard, ChessMove> gameData) {
//			setMaxScroll(moveScrollPanel.getVerticalScrollBar());
			if (gameData.getLastEntry().getBoard().getAnalysisData() != null) {
				setMaxScroll(analysisScrollPanel.getVerticalScrollBar());
			}
		}
	}
	
	private void setMaxScroll(final JScrollBar scrollBar) {
		SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
			public Void doInBackground() {
				scrollBar.setValue(scrollBar.getMaximum() - scrollBar.getVisibleAmount());
				return null;
			}
		};
		worker.execute();
	}
	
	private void displayComment(final ChessBoard board) {
		SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
			public Void doInBackground() {
				if (board.getAnalysisData() != null) {
					moveExtraInfo.setText("<html>" + board.getAnalysisData().getComment() + "</html>");
				} else {
					moveExtraInfo.setText("");
				}
				return null;
			}
		};
		worker.execute();
	}

	class CurrentGameWriter implements ChessGameListener, AnalysisListener {
		
		File file = new File (currentGameFileName);
		public void onGameUpdate (GameData<ChessBoard, ChessMove> gameData) {
			ChessGameData chessGameData = (ChessGameData)gameData;
			writeGame(chessGameData);
		}
		
		public void onAnalysisUpdate (ChessGameData chessGameData) {
			writeGame (chessGameData);
		}
		
		private void writeGame(ChessGameData chessGameData) {
			try {
				FileOutputStream saveFileStream = new FileOutputStream(file);
				saveFileStream.write(chessGameData.getPgn().getBytes());
				saveFileStream.close();
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
	}

	class AnalysisTableUpdater implements ChessGameListener, AnalysisListener {
		
		public TableModelAnalysisData tableModelAnalysisData;
		
		public AnalysisTableUpdater(TableModelAnalysisData tableModelAnalysisData) {
			this.tableModelAnalysisData = tableModelAnalysisData;
		}
		
		public void onGameUpdate (GameData<ChessBoard, ChessMove> gameData) {
			tableModelAnalysisData.gameData = (ChessGameData)gameData;
			doUpdateInSwingWorker();
		}
		
		public void onAnalysisUpdate (ChessGameData chessGameData) {
			doUpdateInSwingWorker();
			if (chessGameData.getLastEntry().getBoard().getAnalysisData() != null) {
				setMaxScroll(analysisScrollPanel.getVerticalScrollBar());
			}
		}
		
		private void doUpdateInSwingWorker() {
			SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
				public Void doInBackground() {
					tableModelAnalysisData.fireTableStructureChanged();
					return null;
				}
			};
			worker.execute();
		}
	}
	
	abstract class TableModelGameData extends AbstractTableModel {
		public ChessGameData gameData;
		
        public ChessGameData getGameData() {
        	return gameData;
        }
	}

	/*
	class TableModelMoveData extends TableModelGameData {
		public ChessMoveParser chessMoveParser = new PGNMoveParser();
        public int getColumnCount() { return 1; }
        public int getRowCount() { if (gameData != null) {return gameData.getMoveDataList().size();} else {return 0;}}
        public Object getValueAt(int row, int col) {
        	if (row == 0) {
        		return "";
        	}
        	if (gameData != null && gameData.getMoveDataList().size() >= row) {
        		ChessBoard board = gameData.getMoveDataList().get(row - 1).getBoard();
        		ChessMove move = gameData.getMoveDataList().get(row - 1).getMove();
        		if (chessBoard != null && move != null) {
        			return ((row - 1) / 2 + 1) + (board.getTurn() == PieceColor.WHITE? ". " : "... ") + chessMoveParser.createMoveString(board, move);
        		}
        	}
        	return "";
        }
	}
	*/
	
	class TableModelAnalysisData extends TableModelGameData {
		public ChessMoveParser chessMoveParser = new PGNMoveParser();
		public int getColumnCount() { return 2; }
		public String getColumnName (int col) {
			if (col == 0) {
				return "Move";
			} else if (col == 1) {
				return "Position Analysis";
			}
			return "";
		}
		public int getRowCount() { if (gameData != null) {return gameData.getMoveDataList().size();} else {return 0;}}
		public Object getValueAt(int row, int col) {
			if (col == 1) {
				if (gameData != null && gameData.getMoveDataList().size() > row) {
					ChessBoard board = gameData.getMoveDataList().get(row).getBoard();
					return board;
				}
			} else if (col == 0) {
				if (row == 0) {
	        		return "";
	        	}
	        	if (gameData != null && gameData.getMoveDataList().size() >= row) {
	        		ChessBoard board = gameData.getMoveDataList().get(row - 1).getBoard();
	        		ChessMove move = gameData.getMoveDataList().get(row - 1).getMove();
	        		if (chessBoard != null && move != null) {
	        			return ((row - 1) / 2 + 1) + (board.getTurn() == PieceColor.WHITE? ". " : "... ") + chessMoveParser.createMoveString(board, move);
	        		}
	        	}
	        	return "";
			}
			return null;
		}
	}
	
	class AnalysisRenderer extends JPanel implements TableCellRenderer {
		
		  ChessBoard cb;
		  boolean selected;
		  Color selectBackground = new Color (170, 170, 255);
		  
		  public Component getTableCellRendererComponent(JTable table, Object value,
                  boolean isSelected, boolean hasFocus, int row, int column) {
			  selected = isSelected;
			  if (column == 1) {
				  cb = (ChessBoard)value;
			  } else if (column == 0) {
				  if (value instanceof ChessBoard) {
					  return null;
				  }
				  JLabel label =  new JLabel((String)value);
				  if (selected) {
					  label.setOpaque(true);
					  label.setBackground(selectBackground);
				  }
				  return label;
			  }
			  return this;
		  }
		  
		  @Override
		  public void paint (Graphics g) {
			  int width = getWidth();
			  int height = getHeight();
			 
			  if (cb == null || cb.getAnalysisData() == null || cb.getAnalysisData().getWhiteScore() == AnalysisData.NOT_SET) {
				  g.setColor(Color.YELLOW);
				  g.fillRect(0, 0, width, height);
			  } else {
			  	  float val = cb.getAnalysisData().getWhiteScore();
				  Color whiteColor ;
				  Color blackColor ;
				  if (cb.getTurn() == PieceColor.WHITE) {
				  	whiteColor = new Color(240, 240, 240);
					blackColor = Color.black;
				  } else {
				  	whiteColor = Color.white;
					blackColor = Color.darkGray;
				  }
				  g.setColor(blackColor);
				  g.fillRect(0, 0, width, height);
				  g.setColor(whiteColor);
				  float factor = 12;
				  float fillVal = val > factor? factor: val;
				  fillVal = val < -factor? -factor: val;
				  g.fillRect(0, 0, width/2 + ((int)(val/factor * width/2)), height);
				  
				  g.setColor(Color.blue);
				  g.drawLine(width/2, 0, width/2, height);
			  }
			  if (selected) {
				  Graphics2D g2d = (Graphics2D)g;
				  g.setColor(Color.BLUE);
				  g2d.setStroke(new BasicStroke(2));
				  g.drawRect(0,0, width - 1, height - 1);
			  }
		  }
	}
	
	class AnalysisTableRowSelectionListener implements ListSelectionListener {
		
		TableModelGameData tableModelGameData;
		
		public AnalysisTableRowSelectionListener (TableModelGameData tableModelGameData) {
			this.tableModelGameData = tableModelGameData;
		}
		
		public void valueChanged (ListSelectionEvent listSelectionEvent) {
			int index = ((ListSelectionModel)listSelectionEvent.getSource()).getMaxSelectionIndex();
			if (index < 0) {
				return;
			}
			GameData<ChessBoard, ChessMove> gameData = tableModelGameData.getGameData();
			MoveData<ChessBoard, ChessMove> moveData = gameData.getMoveDataList().get(index);
			displayComment(moveData.getBoard());
			boardGUI.setBoard(moveData.getBoard());
			if (index > 0) {
				ChessMove lastMove = gameData.getMoveDataList().get(index - 1).getMove();
				boardGUI.setLastMove(lastMove);
			}
			if ((gameData.getMoveDataList().size() - 1) == index) {
				chessGame.startGame();
			} else {
				chessGame.stopGame();
			}
			//moveTable.setRowSelectionInterval(index, index);
			analysisTable.setRowSelectionInterval(index, index);
			moveExtraInfo.repaint();
		}
	}
	
	class FlipBoardItemListener implements ItemListener {

		@Override
		public void itemStateChanged(ItemEvent event) {
			if (event.getStateChange() == ItemEvent.SELECTED) {
				boardGUI.setFlip(true);
			} else {
				boardGUI.setFlip(false);
			}
			boardGUI.repaint();
		}
		
	}
	
	class ToggleAnalyserItemListener implements ItemListener {
		@Override
		public void itemStateChanged(ItemEvent event) {
			if (event.getStateChange() == ItemEvent.SELECTED) {
				chessGameAnalyser.stopAnalysis();
			} else {
				chessGameAnalyser.startAnalysis();
			}
		}		
	}
	
	class ToggleHintListener implements ItemListener {
		ChessBoardRenderProperties boardRenderProperties;
		public ToggleHintListener (ChessBoardRenderProperties boardRenderProperties) {
			this.boardRenderProperties = boardRenderProperties;
		}
		@Override
		public void itemStateChanged(ItemEvent event) {
			if (event.getStateChange() == ItemEvent.SELECTED) {
				boardRenderProperties.setDisplayHint(true);
			} else {
				boardRenderProperties.setDisplayHint(false);
			}
			boardGUI.repaint();
		}		
	}

	
	class BlackComputerItemListener implements ItemListener {
		@Override
		public void itemStateChanged(ItemEvent event) {
			chessGame.stopGame();
			if (event.getStateChange() == ItemEvent.SELECTED) {
				chessGame.setBlackPlayer(computerBlackPlayer);
			} else {
				chessGame.setBlackPlayer(humanBlackPlayer);
			}
			chessGame.startGame();
		}
	}
	
	class WhiteComputerItemListener implements ItemListener {
		@Override
		public void itemStateChanged(ItemEvent event) {
			chessGame.stopGame();
			if (event.getStateChange() == ItemEvent.SELECTED) {
				chessGame.setWhitePlayer(computerWhitePlayer);
			} else {
				chessGame.setWhitePlayer(humanWhitePlayer);
			}
			chessGame.startGame();
		}
	}
	
	class TruncateButtonAction extends AbstractAction {

		@Override
		public void actionPerformed(ActionEvent arg0) {
			// TODO Auto-generated method stub
			int index = analysisTable.getSelectedRow();
			if (index >= 0) {
				chessGame.stopGame();
				ChessGameData gameData = chessGame.getGameData();
				for (int i = gameData.getMoveDataList().size() - 1; i > index; i--) {
					gameData.getMoveDataList().remove(i);
				}
				gameData.getMoveDataList().get(index).setMove(null);
				chessGame.setGameData(gameData);
				chessGame.updateGameListeners();
				chessGame.startGame();
			}
		}	
	}
	
	class BackButtonAction extends AbstractAction {

		@Override
		public void actionPerformed(ActionEvent arg0) {
			// TODO Auto-generated method stub
			int index = analysisTable.getSelectedRow();
			ChessGameData gameData = chessGame.getGameData();
			List<MoveData<ChessBoard, ChessMove>> moveData = gameData.getMoveDataList();
			if ((moveData.size() > 1 && ((moveData.size() - 1) == index || index < 0))) {
				chessGame.stopGame();
				moveData.remove(moveData.size() - 1);
				moveData.get(moveData.size() - 1).setMove(null);
				chessGame.setGameData(gameData);
				chessGame.updateGameListeners();
				chessGame.startGame();
			} 
		}	
	}

	
	class GoButtonAction extends AbstractAction {

		@Override
		public void actionPerformed(ActionEvent arg0) {
			String command = commandText.getText();
			FENSerializer fenSerializer = new FENSerializer();
			ChessBoard cb = fenSerializer.getBoard(command);
			if (cb != null) {
				chessGame.setChessBoard(cb);
				return;
			}
			cb = (ChessBoard) chessGame.getGameData().getLastEntry().getBoard();
			CoordinateMoveParser coordinateMoveParser = new CoordinateMoveParser();
			ChessMove cm = coordinateMoveParser.parseMoveString (cb, commandText.getText());
			if (cm != null) {
				chessGame.forceMove (cm);
				return;
			}
			PGNMoveParser pgnMoveParser = new PGNMoveParser();
			cm = pgnMoveParser.parseMoveString (cb, commandText.getText());
			if (cm != null) {
				chessGame.forceMove (cm);
				return;
			}
		}
	}
	
	class GameDataEditorDialog extends JDialog {
		JTextArea textArea;
		public GameDataEditorDialog (JFrame parent) {
			super(parent);
			setSize(400, 400);
			setModal(true);
			setVisible(false);
			setLayout(new BorderLayout());
			textArea = new JTextArea();
			JScrollPane textAreaScrollPanel = new JScrollPane();
			textAreaScrollPanel.setViewportView(textArea);
			this.add(textAreaScrollPanel, BorderLayout.CENTER);
			JButton okButton = new JButton("Ok");
			JPanel buttonPanel = new JPanel();
			buttonPanel.add(okButton);
			this.add(buttonPanel, BorderLayout.SOUTH);
			okButton.setAction(new AbstractAction("Ok") {
				@Override
				public void actionPerformed(ActionEvent ae) {
					handleOkButton();
				}
			});
		}
		
		public void handleOkButton() {
			this.setVisible(false);
		}
		
		public String getText() {
			return textArea.getText();
		}
	}
}
